﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MethodWorx.Core.DomainModel;
using MethodWorx.Core.Extensions;
using System.Data.Linq;

namespace MethodWorx.Core.Linq
{
    public class RepositoryAction<TLinqEntity>
    {
        public TLinqEntity Entity { get; set; }
        public RepositoryActionType Type { get; set; }
    }

    public enum RepositoryActionType
    {
        Inserted,
        Deleted,
        Unchanged
    }

    public abstract class LinqRepository<TLinqEntity, TAggregateRoot> 
        : QueryableRepository<TAggregateRoot>, IRefreshLinqEntity
        where TLinqEntity : TAggregateRoot
        where TAggregateRoot : AggregateRoot
    {

        protected DataContext context;

        public LinqRepository(DataContext context)
            : base(context.GetTable<TLinqEntity>().Cast<TAggregateRoot>())
        {
            this.context = context;
        }

        public override TAggregateRoot OnCreateEntityInstance()
        {
            TLinqEntity linq = Activator.CreateInstance<TLinqEntity>();
            linq.Id = Guid.NewGuid();

            return (TAggregateRoot)linq;
        }

        public override void Delete(TAggregateRoot entity)
        {
            //
            //  let SQL Cascading changes handle the delete!
            this.context.GetTable<TLinqEntity>().DeleteOnSubmit((TLinqEntity)entity);
            this.OnDelete((TLinqEntity)entity);
        }

        protected virtual void OnDelete(TLinqEntity entity)
        {
        }

        public override void Insert(TAggregateRoot entity)
        {
            //  insert the entity here
            this.context.GetTable<TLinqEntity>().InsertOnSubmit((TLinqEntity)entity);
            this.OnInsert((TLinqEntity)entity);
        }

        protected virtual void OnInsert(TLinqEntity entity)
        {
        }

        public override void Update(TAggregateRoot entity)
        {
            //
            //  ok, its important we go through all the associations on this object
            //  and if its an entityset then we call update child entity set
            //  during an update we dont have to do anything!
            this.OnUpdate((TLinqEntity)entity);
        }

        protected virtual void OnUpdate(TLinqEntity entity)
        {
        }

        /// <summary>
        /// Allow updating of many to many relationships, or strange relationship mappings accross child entities
        /// </summary>
        /// <typeparam name="TEntitySetProxy">The child proxy entity type</typeparam>
        /// <typeparam name="TParent">The parent entity domain type</typeparam>
        /// <typeparam name="TChildEntity">The child entity domain type</typeparam>
        /// <param name="entitySet">The child entity set</param>
        /// <param name="collection">The child domain collection</param>
        /// <param name="proxyLocator">The delegate used to locate the proxy entity in entity set that matches the domain entity in the collection</param>
        /// <param name="domainLocator">The delegate used to locate the domain entity in the collection that matches the proxy entity in the entity set</param>
        /// <param name="attach">The delegate used to perform the "attachment" which updates the linq entity that attaches the entities together</param>
        protected IEnumerable<RepositoryAction<TEntitySetProxy>> UpdateChildEntitySet<TEntitySetProxy, TParent, TChildEntity>(
            IEnumerable<TEntitySetProxy> entitySet, 
            ChildEntityCollection<TParent, TChildEntity> collection,
            Func<IEnumerable<TEntitySetProxy>, TChildEntity, TEntitySetProxy> proxyLocator,
            Func<ChildEntityCollection<TParent, TChildEntity>, TEntitySetProxy, TChildEntity> domainLocator,
            Action<TEntitySetProxy, TChildEntity> attach)

            where TEntitySetProxy : class
            where TChildEntity : Entity
        {
            //  also insert the aggregate root items
            foreach (TChildEntity domain in collection)
            {
                //
                //  attempt to find the task from the linq entity
                //  if its not in here then its an insert
                TEntitySetProxy proxy = proxyLocator(entitySet, domain);
                if (proxy == null)
                {
                    //
                    //  ok, insert the task
                    proxy = Activator.CreateInstance<TEntitySetProxy>();
                    //  copy the object over from the linq object
                    attach(proxy, domain);

                    //  this is an insert
                    this.context.GetTable<TEntitySetProxy>().InsertOnSubmit((TEntitySetProxy)proxy);
                    yield return
                        new RepositoryAction<TEntitySetProxy>
                        {
                            Entity = proxy,
                            Type = RepositoryActionType.Inserted
                        };
                }
            }

            foreach (TEntitySetProxy proxy in entitySet)
            {
                TChildEntity domain = domainLocator(collection, proxy);
                if (domain == null)
                {
                    this.context.GetTable<TEntitySetProxy>().DeleteOnSubmit((TEntitySetProxy)proxy);
                    yield return
                        new RepositoryAction<TEntitySetProxy>
                        {
                            Entity = proxy,
                            Type = RepositoryActionType.Deleted
                        };
                }
                else
                {
                    yield return
                        new RepositoryAction<TEntitySetProxy>
                        {
                            Entity = proxy,
                            Type = RepositoryActionType.Unchanged
                        };
                }
            }
        }

        protected IEnumerable<RepositoryAction<TEntitySetProxy>> UpdateChildEntitySet<TEntitySetProxy, TParent, TChildEntity>(IEnumerable<TEntitySetProxy> entitySet, ChildEntityCollection<TParent, TChildEntity> collection, Action<TEntitySetProxy> attach)
            where TEntitySetProxy : class, TChildEntity
            where TChildEntity : Entity
        {
            //  also insert the aggregate root items
            foreach (TChildEntity domain in collection)
            {
                //
                //  attempt to find the task from the linq entity
                //  if its not in here then its an insert
                TEntitySetProxy proxy = entitySet.FirstOrDefault(pt => pt.Id == domain.Id);
                if (proxy == null)
                {
                    //
                    //  ok, insert the task
                    proxy = Activator.CreateInstance<TEntitySetProxy>();
                    //  copy the object over from the linq object
                    proxy.CopyObject(domain);
                    attach(proxy);

                    //  this is an insert
                    this.context.GetTable<TEntitySetProxy>().InsertOnSubmit((TEntitySetProxy)proxy);
                    yield return
                       new RepositoryAction<TEntitySetProxy>
                       {
                           Entity = proxy,
                           Type = RepositoryActionType.Inserted
                       };
                }
            }

            foreach (TEntitySetProxy proxy in entitySet)
            {
                TChildEntity domain = collection.FirstOrDefault(t => t.Id == proxy.Id);
                if (domain == null)
                {
                    this.context.GetTable<TEntitySetProxy>().DeleteOnSubmit((TEntitySetProxy)proxy);
                    yield return
                       new RepositoryAction<TEntitySetProxy>
                       {
                           Entity = proxy,
                           Type = RepositoryActionType.Deleted
                       };
                }
                else
                {
                    yield return
                       new RepositoryAction<TEntitySetProxy>
                       {
                           Entity = proxy,
                           Type = RepositoryActionType.Unchanged
                       };
                }
            }
        }

        public void Refresh(AggregateRoot entity)
        {
            //  refresh the entity 
            this.context.Refresh(RefreshMode.OverwriteCurrentValues, entity);
            this.OnRefresh((TLinqEntity)entity);
        }

        protected virtual void OnRefresh(TLinqEntity entity)
        {
        }
    }
}
